ποΈGitΠ―ΡΠ°ποΈ
Node / meshtastic / Meshtastic-Android / files / core / database / src / commonMain / kotlin / org / meshtastic / core / database / DatabaseMerger.kt
Displaying Raw β’ Download
core/database/src/commonMain/kotlin/org/meshtastic/core/database/DatabaseMerger.kt d5848ad5e02bd9b5b1726bedfa51ceb0faaa0240 (d5848ad5) Text, 11.59 KB
T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.core.database
Tff7b72import T7ee787androidx.room3.immediateTransaction
Tff7b72import T7ee787androidx.room3.useWriterConnection
Tff7b72import T7ee787co.touchlab.kermit.Logger
Tff7b72import T7ee787org.meshtastic.core.common.util.nowMillis
Tff7b72import T7ee787org.meshtastic.core.database.entity.MergeMarkerEntity
T8b949e/**
* Folds the contents of one device database ([source]) into another ([dest]) when both turn out to belong to the same
* physical node reached over different transports (BLE / TCP / USB). Called once, by [DatabaseManager.associateDevice],
* the first time a secondary transport learns a `myNodeNum` that another transport already claimed.
*
* Every per-device table is unified so switching transport is seamless: messages (+FTS), reactions, contact mute/read
* settings, nodes and their notes, per-node metadata, quick-chat actions, the audit log (each session's received-packet
* history β the source of the telemetry timelines, position history, and traceroute results the UI reconstructs),
* traceroute positions, and discovery sessions. Where the same row exists on both sides the destination is preferred
* (it will be refreshed by the same radio's re-dump on connect); source-only rows and strictly newer history are
* brought over. Packets/reactions in [source] and [dest] already share the same `myNodeNum` (same node), so no key
* remapping is needed there; autoincrement-keyed rows (packets, discovery) are re-inserted with fresh ids to avoid
* collisions.
*/
Tff7b72internal Tff7b72class T56d364StaleAssociationException Tb4b4b4: Te6edf3ExceptionTb4b4b4(Ta5d6ff"Ta5d6ffTransport session no longer authorizes database associationTa5d6ff"Tb4b4b4)
Tff7b72object T56d364DatabaseMerger Tb4b4b4{
T8b949e/**
* Folds [source] into [dest], skipping the work if [sourceName] was already merged into [dest]. [sourceName] is the
* source database's file name β the stable key under which the merge is recorded (see [MergeMarkerEntity]).
*
* The caller owns the transport-session lifecycle lease through this method's return. That lease makes transaction
* commit and session rollover mutually exclusive; [isAssociationActive] remains a defensive check for authority
* lost before the merge entered its commit phase.
*/
Tff7b72suspend Tff7b72fun Td2a8ffmergeTb4b4b4(
Te6edf3sourceTb4b4b4: Te6edf3MeshtasticDatabaseTb4b4b4,
Te6edf3destTb4b4b4: Te6edf3MeshtasticDatabaseTb4b4b4,
Te6edf3sourceNameTb4b4b4: Tffa657StringTb4b4b4,
Te6edf3isAssociationActiveTb4b4b4: Tb4b4b4(Tb4b4b4) Tff7b72-Tff7b72> Tffa657Boolean Tff7b72= Tb4b4b4{ Tff7b72true Tb4b4b4}Tb4b4b4,
Tb4b4b4) Tb4b4b4{
T8b949e// All destination writes run in a single transaction so a crash or exception mid-merge rolls
T8b949e// back cleanly instead of leaving `dest` half-merged. The merge marker is written inside that
T8b949e// same transaction, so it commits atomically with the copied rows: on a retried merge (e.g. a
T8b949e// crash after commit but before associateDevice persisted the address alias) the marker is already
T8b949e// present and the whole merge is skipped, so the fresh-id packet/discovery re-inserts β which are
T8b949e// NOT idempotent on their own β can never duplicate. Reads from `source` use its own connection
T8b949e// pool and don't participate in this transaction.
Tff7b72var Te6edf3packets Tff7b72= T79c0ff0
Tff7b72var Te6edf3skipped Tff7b72= Tff7b72false
Te6edf3destTb4b4b4.Te6edf3useWriterConnection Tb4b4b4{ Te6edf3transactor Tff7b72-Tff7b72>
Te6edf3transactorTb4b4b4.Te6edf3immediateTransaction Tb4b4b4{
Te6edf3ensureAssociationActiveTb4b4b4(Te6edf3isAssociationActiveTb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3destTb4b4b4.Te6edf3mergeMarkerDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3isMergedTb4b4b4(Te6edf3sourceNameTb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3ensureAssociationActiveTb4b4b4(Te6edf3isAssociationActiveTb4b4b4)
Te6edf3skipped Tff7b72= Tff7b72true
Tff7b72returnTf0883e@immediateTransaction
Tb4b4b4}
Te6edf3packets Tff7b72= Te6edf3mergePacketsTb4b4b4(Te6edf3sourceTb4b4b4, Te6edf3destTb4b4b4)
Te6edf3mergeReactionsTb4b4b4(Te6edf3sourceTb4b4b4, Te6edf3destTb4b4b4)
Te6edf3mergeContactSettingsTb4b4b4(Te6edf3sourceTb4b4b4, Te6edf3destTb4b4b4)
Te6edf3mergeNodesTb4b4b4(Te6edf3sourceTb4b4b4, Te6edf3destTb4b4b4)
Te6edf3mergeMetadataTb4b4b4(Te6edf3sourceTb4b4b4, Te6edf3destTb4b4b4)
Te6edf3mergeQuickChatTb4b4b4(Te6edf3sourceTb4b4b4, Te6edf3destTb4b4b4)
T8b949e// Logs must precede traceroute positions: the latter has a CASCADE foreign key onto log.uuid.
Te6edf3mergeLogsTb4b4b4(Te6edf3sourceTb4b4b4, Te6edf3destTb4b4b4)
Te6edf3mergeTraceroutePositionsTb4b4b4(Te6edf3sourceTb4b4b4, Te6edf3destTb4b4b4)
Te6edf3mergeDiscoveryTb4b4b4(Te6edf3sourceTb4b4b4, Te6edf3destTb4b4b4)
Te6edf3destTb4b4b4.Te6edf3mergeMarkerDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3insertMarkerTb4b4b4(Te6edf3MergeMarkerEntityTb4b4b4(Te6edf3sourceDbName Tff7b72= Te6edf3sourceNameTb4b4b4, Te6edf3mergedAt Tff7b72= Te6edf3nowMillisTb4b4b4)Tb4b4b4)
T8b949e// Keep the defensive authority check as the final transaction statement. The caller-held lifecycle
T8b949e// lease prevents rollover until commit returns; this check still rolls back if authority was lost
T8b949e// before
T8b949e// the transaction entered the lease-protected commit phase.
Te6edf3ensureAssociationActiveTb4b4b4(Te6edf3isAssociationActiveTb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3skippedTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffSource Tffd700${Te6edf3anonymizeDbNameTb4b4b4(Te6edf3sourceNameTb4b4b4)Tffd700}Ta5d6ff already merged; skipped duplicate mergeTa5d6ff" Tb4b4b4}
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffMerged Tffd700$Te6edf3packetsTa5d6ff packets across transports into unified node DBTa5d6ff" Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8ffensureAssociationActiveTb4b4b4(Te6edf3isAssociationActiveTb4b4b4: Tb4b4b4(Tb4b4b4) Tff7b72-Tff7b72> Tffa657BooleanTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3isAssociationActiveTb4b4b4(Tb4b4b4)Tb4b4b4) Tff7b72throw Te6edf3StaleAssociationExceptionTb4b4b4(Tb4b4b4)
Tb4b4b4}
T8b949e/**
* Message history is the primary payload. `uuid` is per-DB autoincrement, so re-insert with uuid = 0 to mint a
* fresh id and avoid collisions; rebuild the FTS index once at the end so copied messages are searchable.
*/
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffmergePacketsTb4b4b4(Te6edf3sourceTb4b4b4: Te6edf3MeshtasticDatabaseTb4b4b4, Te6edf3destTb4b4b4: Te6edf3MeshtasticDatabaseTb4b4b4)Tb4b4b4: Tffa657Int Tb4b4b4{
Tff7b72val Te6edf3packets Tff7b72= Te6edf3sourceTb4b4b4.Te6edf3packetDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3getAllPacketsSnapshotTb4b4b4(Tb4b4b4)
Te6edf3packetsTb4b4b4.Te6edf3forEach Tb4b4b4{ Te6edf3destTb4b4b4.Te6edf3packetDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3insertPacketForMergeTb4b4b4(Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3uuid Tff7b72= T79c0ff0LTb4b4b4)Tb4b4b4) Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3packetsTb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4)Tb4b4b4) Te6edf3destTb4b4b4.Te6edf3packetDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3rebuildFtsIndexTb4b4b4(Tb4b4b4)
Tff7b72return Te6edf3packetsTb4b4b4.Te6edf3size
Tb4b4b4}
T8b949e/** Composite PK (myNodeNum, reply_id, user_id, emoji) makes IGNORE a natural dedupe. */
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffmergeReactionsTb4b4b4(Te6edf3sourceTb4b4b4: Te6edf3MeshtasticDatabaseTb4b4b4, Te6edf3destTb4b4b4: Te6edf3MeshtasticDatabaseTb4b4b4) Tb4b4b4{
Te6edf3destTb4b4b4.Te6edf3packetDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3insertReactionsIgnoreTb4b4b4(Te6edf3sourceTb4b4b4.Te6edf3packetDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3getAllReactionsSnapshotTb4b4b4(Tb4b4b4)Tb4b4b4)
Tb4b4b4}
T8b949e/** Keep the destination's settings on conflict (IGNORE); only bring over contacts it has never seen. */
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffmergeContactSettingsTb4b4b4(Te6edf3sourceTb4b4b4: Te6edf3MeshtasticDatabaseTb4b4b4, Te6edf3destTb4b4b4: Te6edf3MeshtasticDatabaseTb4b4b4) Tb4b4b4{
Te6edf3destTb4b4b4.Te6edf3packetDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3insertContactSettingsIgnoreTb4b4b4(Te6edf3sourceTb4b4b4.Te6edf3packetDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3getAllContactSettingsSnapshotTb4b4b4(Tb4b4b4)Tb4b4b4)
Tb4b4b4}
T8b949e/**
* Bring over every node the destination has never seen (so its last-known identity/telemetry/position shows
* immediately, including offline nodes the radio may not re-report). For overlapping nodes, keep the destination's
* row β the same radio re-dumps it on connect β but fill in a user note where the destination has none, never
* clobbering one the user already wrote on the canonical DB.
*/
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffmergeNodesTb4b4b4(Te6edf3sourceTb4b4b4: Te6edf3MeshtasticDatabaseTb4b4b4, Te6edf3destTb4b4b4: Te6edf3MeshtasticDatabaseTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3destNums Tff7b72= Te6edf3destTb4b4b4.Te6edf3nodeInfoDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3getAllNodesSnapshotTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3mapToTb4b4b4(Te6edf3mutableSetOfTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3num Tb4b4b4}
Te6edf3sourceTb4b4b4.Te6edf3nodeInfoDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3getAllNodesSnapshotTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3forEach Tb4b4b4{ Te6edf3node Tff7b72-Tff7b72>
Tff7b72when Tb4b4b4{
Te6edf3nodeTb4b4b4.Te6edf3num Tff7b72!in Te6edf3destNums Tff7b72-Tff7b72> Te6edf3destTb4b4b4.Te6edf3nodeInfoDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3upsertTb4b4b4(Te6edf3nodeTb4b4b4)
Te6edf3nodeTb4b4b4.Te6edf3notesTb4b4b4.Te6edf3isNotBlankTb4b4b4(Tb4b4b4) Tff7b72-Tff7b72> Tb4b4b4{
Tff7b72val Te6edf3existing Tff7b72= Te6edf3destTb4b4b4.Te6edf3nodeInfoDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3getNodeByNumTb4b4b4(Te6edf3nodeTb4b4b4.Te6edf3numTb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3existing Tff7b72!Tff7b72= Tff7b72null Tff7b72&Tff7b72& Te6edf3existingTb4b4b4.Te6edf3nodeTb4b4b4.Te6edf3notesTb4b4b4.Te6edf3isBlankTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3destTb4b4b4.Te6edf3nodeInfoDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3setNodeNotesTb4b4b4(Te6edf3nodeTb4b4b4.Te6edf3numTb4b4b4, Te6edf3nodeTb4b4b4.Te6edf3notesTb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
T8b949e/** Per-node DeviceMetadata: bring source rows the dest lacks or that are strictly newer (by timestamp). */
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffmergeMetadataTb4b4b4(Te6edf3sourceTb4b4b4: Te6edf3MeshtasticDatabaseTb4b4b4, Te6edf3destTb4b4b4: Te6edf3MeshtasticDatabaseTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3destByNum Tff7b72= Te6edf3destTb4b4b4.Te6edf3nodeInfoDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3getAllMetadataSnapshotTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3associateBy Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3num Tb4b4b4}
Te6edf3sourceTb4b4b4.Te6edf3nodeInfoDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3getAllMetadataSnapshotTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3forEach Tb4b4b4{ Te6edf3meta Tff7b72-Tff7b72>
Tff7b72val Te6edf3existing Tff7b72= Te6edf3destByNumTff7b72[Te6edf3metaTb4b4b4.Te6edf3numTff7b72]
Tff7b72if Tb4b4b4(Te6edf3existing Tff7b72=Tff7b72= Tff7b72null Tff7b72|Tff7b72| Te6edf3metaTb4b4b4.Te6edf3timestamp Tff7b72> Te6edf3existingTb4b4b4.Te6edf3timestampTb4b4b4) Te6edf3destTb4b4b4.Te6edf3nodeInfoDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3upsertTb4b4b4(Te6edf3metaTb4b4b4)
Tb4b4b4}
Tb4b4b4}
T8b949e/**
* User-authored quick-chat buttons. `uuid`/`position` are per-DB, so append the source's actions after the
* destination's last one with fresh ids, skipping any the destination already has (same name + message + mode).
* Without this the source's buttons are lost outright when [DatabaseManager] retires it.
*/
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffmergeQuickChatTb4b4b4(Te6edf3sourceTb4b4b4: Te6edf3MeshtasticDatabaseTb4b4b4, Te6edf3destTb4b4b4: Te6edf3MeshtasticDatabaseTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3destActions Tff7b72= Te6edf3destTb4b4b4.Te6edf3quickChatActionDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3getAllSnapshotTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3existing Tff7b72= Te6edf3destActionsTb4b4b4.Te6edf3mapToTb4b4b4(Te6edf3mutableSetOfTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{ Te6edf3TripleTb4b4b4(Tffa657itTb4b4b4.Te6edf3nameTb4b4b4, Tffa657itTb4b4b4.Te6edf3messageTb4b4b4, Tffa657itTb4b4b4.Te6edf3modeTb4b4b4) Tb4b4b4}
Tff7b72var Te6edf3nextPosition Tff7b72= Tb4b4b4(Te6edf3destActionsTb4b4b4.Te6edf3maxOfOrNull Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3position Tb4b4b4} Tff7b72?: Tff7b72-T79c0ff1Tb4b4b4) Tff7b72+ T79c0ff1
Te6edf3sourceTb4b4b4.Te6edf3quickChatActionDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3getAllSnapshotTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3forEach Tb4b4b4{ Te6edf3action Tff7b72-Tff7b72>
Tff7b72if Tb4b4b4(Te6edf3existingTb4b4b4.Te6edf3addTb4b4b4(Te6edf3TripleTb4b4b4(Te6edf3actionTb4b4b4.Te6edf3nameTb4b4b4, Te6edf3actionTb4b4b4.Te6edf3messageTb4b4b4, Te6edf3actionTb4b4b4.Te6edf3modeTb4b4b4)Tb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3destTb4b4b4.Te6edf3quickChatActionDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3upsertTb4b4b4(Te6edf3actionTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3uuid Tff7b72= T79c0ff0LTb4b4b4, Te6edf3position Tff7b72= Te6edf3nextPositionTb4b4b4)Tb4b4b4)
Te6edf3nextPositionTff7b72+Tff7b72+
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
T8b949e/**
* The audit log holds each transport session's received-packet history β the source of the telemetry timelines,
* position history, and node-info history the UI reconstructs per node/port. This is the only place that time
* series diverges between transports (the current node snapshot is re-dumped identically by the same radio), so
* merging it is what actually unifies "metrics/telemetry/positions". uuid is a unique string, so IGNORE is safe.
*/
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffmergeLogsTb4b4b4(Te6edf3sourceTb4b4b4: Te6edf3MeshtasticDatabaseTb4b4b4, Te6edf3destTb4b4b4: Te6edf3MeshtasticDatabaseTb4b4b4) Tb4b4b4{
Te6edf3destTb4b4b4.Te6edf3meshLogDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3insertIgnoreTb4b4b4(Te6edf3sourceTb4b4b4.Te6edf3meshLogDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3getAllLogsSnapshotTb4b4b4(Tb4b4b4)Tb4b4b4)
Tb4b4b4}
T8b949e/**
* Traceroute-derived positions, keyed to their log entry (CASCADE FK) β merge after [mergeLogs] so the parent log
* rows already exist in [dest].
*/
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffmergeTraceroutePositionsTb4b4b4(Te6edf3sourceTb4b4b4: Te6edf3MeshtasticDatabaseTb4b4b4, Te6edf3destTb4b4b4: Te6edf3MeshtasticDatabaseTb4b4b4) Tb4b4b4{
Te6edf3destTb4b4b4.Te6edf3tracerouteNodePositionDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3insertAllTb4b4b4(Te6edf3sourceTb4b4b4.Te6edf3tracerouteNodePositionDaoTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3getAllSnapshotTb4b4b4(Tb4b4b4)Tb4b4b4)
Tb4b4b4}
T8b949e/**
* Discovery sessions are distinct events per transport session, so append them all (no dedupe). The three tables
* form an autoincrement-keyed FK chain (session β preset result β discovered node), so re-insert each level with a
* fresh id and rewire the child's foreign key to the parent's newly-assigned id.
*/
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffmergeDiscoveryTb4b4b4(Te6edf3sourceTb4b4b4: Te6edf3MeshtasticDatabaseTb4b4b4, Te6edf3destTb4b4b4: Te6edf3MeshtasticDatabaseTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3src Tff7b72= Te6edf3sourceTb4b4b4.Te6edf3discoveryDaoTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3dst Tff7b72= Te6edf3destTb4b4b4.Te6edf3discoveryDaoTb4b4b4(Tb4b4b4)
Te6edf3srcTb4b4b4.Te6edf3getAllSessionsSnapshotTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3forEach Tb4b4b4{ Te6edf3session Tff7b72-Tff7b72>
Tff7b72val Te6edf3newSessionId Tff7b72= Te6edf3dstTb4b4b4.Te6edf3insertSessionTb4b4b4(Te6edf3sessionTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3id Tff7b72= T79c0ff0Tb4b4b4)Tb4b4b4)
Te6edf3srcTb4b4b4.Te6edf3getPresetResultsTb4b4b4(Te6edf3sessionTb4b4b4.Te6edf3idTb4b4b4)Tb4b4b4.Te6edf3forEach Tb4b4b4{ Te6edf3preset Tff7b72-Tff7b72>
Tff7b72val Te6edf3newPresetId Tff7b72= Te6edf3dstTb4b4b4.Te6edf3insertPresetResultTb4b4b4(Te6edf3presetTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3id Tff7b72= T79c0ff0Tb4b4b4, Te6edf3sessionId Tff7b72= Te6edf3newSessionIdTb4b4b4)Tb4b4b4)
Tff7b72val Te6edf3nodes Tff7b72= Te6edf3srcTb4b4b4.Te6edf3getDiscoveredNodesTb4b4b4(Te6edf3presetTb4b4b4.Te6edf3idTb4b4b4)Tb4b4b4.Te6edf3map Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3id Tff7b72= T79c0ff0Tb4b4b4, Te6edf3presetResultId Tff7b72= Te6edf3newPresetIdTb4b4b4) Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3nodesTb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4)Tb4b4b4) Te6edf3dstTb4b4b4.Te6edf3insertDiscoveredNodesTb4b4b4(Te6edf3nodesTb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Served by rngit 1.5.0 - Generated in 0.1s